'''
Created on Jul 26, 2010

@author: adolfo

Implementation of the SMSGateway interface for the Kannel SMS gateway. For more
information about Kannel, visit `its homepage
<http://www.kannel.org>`_.
'''
from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer
from SocketServer import ThreadingMixIn
from sms_gateway import Message, SMSGateway
import logging
import threading
import urllib

_LOGGER = logging.getLogger("mx.org.came.kannel_sms_gateway")

class KannelSMSGateway(SMSGateway):
    """Implementation of SMSGateway for the Kannel SMS gateway. In order to
    receive messages, this class starts an HTTP server at the specified port and
    waits for GET requests from Kannel. For information on the URL parameters
    Kannel may send in the GET request, read Kannel's User's Guide.
    
    Note: It is recommended that only one instance of this class is created.
    """

    # URL parameter for the phone number of the sender of the SMS message.
    _SENDER_PHONE_NUMBER_URL_PARAMETER = "%p"
    
    # URL parameter for the phone number of the receiver of the SMS message.
    _RECEIVER_PHONE_NUMBER_URL_PARAMETER = "%P"
    
    # URL parameter for the original SMS message, in a binary form.
    _ORIGINAL_BINARY_MESSAGE_URL_PARAMETER = "%b"
    
    # URL parameter for the message's user data header.
    _UDH_URL_PARAMETER = "%u"

    @staticmethod
    def _init_parameters(kannel_url_parameters):
        """
        Returns a list with the constants defined in ``sms_gateway.Message``
        in the order the corresponding parameters appear in
        ``kannel_url_parameters``.
        """
        key_mapping = {KannelSMSGateway._SENDER_PHONE_NUMBER_URL_PARAMETER : Message.SENDER_PHONE_NUMBER
                       , KannelSMSGateway._RECEIVER_PHONE_NUMBER_URL_PARAMETER : Message.RECEIVER_PHONE_NUMBER
                       , KannelSMSGateway._ORIGINAL_BINARY_MESSAGE_URL_PARAMETER : Message.ORIGINAL_BINARY_MESSAGE
                       , KannelSMSGateway._UDH_URL_PARAMETER : Message.UDH}
        param_list = kannel_url_parameters.split("&")
        kannel_message_parameters = []
        for param in param_list:
            kannel_message_parameters.append(key_mapping[param])
        return kannel_message_parameters
    
    
    def __init__(self
                 , notification_server_address = "localhost"
                 , notification_server_port = 55555
                 , kannel_server_address = "127.0.0.1"
                 , kannel_server_port = 13013
                 , kannel_user = "kanneluser"
                 , kannel_password = "kanneluser"
                 , kannel_phone_number = 1234
                 , kannel_url_parameters="%p&%u&%b&%P"):
        """Initializes the instance of KannelSMSGateway. Sets the listener list
        to the empty list. The message notification server's address defaults to
        "localhost" and its port to 55555 (this is where the instance of
        KannelSMSGateway will be notified). Kannel's configuration defaults to
        server address "127.0.0.1", port 13013, user name to "kanneluser"
        and password "kanneluser", the phone Kannel use as sender to 1234, and
        the URL parameters to "%p&%u&%b&%P".
        """
        _LOGGER.debug("Initializing KannelSMSGateway")
        self._listeners = []
        self._kannel_server_address = kannel_server_address
        self._kannel_server_port = kannel_server_port
        self._kannel_user = kannel_user
        self._kannel_password = kannel_password
        self._kannel_phone_number = kannel_phone_number
        self._kannel_parameters = KannelSMSGateway._init_parameters(kannel_url_parameters)
        # Nested class that handles the request. Attribute 'owner' should be an
        # instance variable instead of a class variable, but the constructor for
        # BaseHTTPRequestHandler should not be overridden..
        # This definition makes _HttpHandler behave as a Java inner class.
        # (as far as I have seen, on the implementation of Python I'm using)
        class _HttpHandler (BaseHTTPRequestHandler):
            """Handles Kannel's requests. These requests contain the data from
            the SMS messages. For example, a GET request to
            http://127.0.0.1:55555/2381280912&&Hello+world&1234
            might mean that we received the message "Hello world" from
            phone number 2381280912
            """
            owner = self
            def do_GET(self):
                """Handles GET requests to the HTTP server"""
                _LOGGER.info("Received SMS message.")
                _LOGGER.debug("Request received: " + self.path)
                self.owner._notify_message_reception(self.path)
        self._notification_server = _MyHttpServer((notification_server_address
                                                   , notification_server_port)
                                                  , _HttpHandler)
        self._server_thread = None
    
    def get_phone_number(self):
        """Returns the phone number of the GSM modem Kannel uses to send and
        receive SMS messages.
        """
        return self._kannel_phone_number
    
    def add_sms_listener(self, sms_listener):
        """Registers an SMSListener to be notified about SMS message events."""
        _LOGGER.debug("Adding new SMSListener to KannelSMSGateway")
        self._listeners.append(sms_listener)
    
    def remove_sms_listener(self, sms_listener):
        """Removes an SMSListener from the list of registered listeners that
        are notified about SMS message events. Only the first appearance of the
        instance is removed.
        """
        _LOGGER.debug("Removing SMSListener from KannelSMSGateway")
        self._listeners.remove(sms_listener)

    def _notify_message_reception(self, kannel_get_path):
        """Transforms the ``kannel_get_path`` into an instance of
        ``sms_gateway.Message`` and notifies all the registered listeners about
        the arrival of the SMS message by invoking their ``message_received``
        method and passing the message as argument.
        """
        _LOGGER.debug("Transforming GET path %s into message.", kannel_get_path)
        message = Message()
        message_data_list = str(kannel_get_path).replace("/", "").split("&")
        for i in range(len(message_data_list)):
            message_data_list[i] = urllib.unquote_plus(message_data_list[i])
        message.data.update(dict(zip(self._kannel_parameters, message_data_list)))
        _LOGGER.debug("Message data: " + str(message.data))
        _LOGGER.debug("Notifying listeners about message arrival.")
        for listener in self._listeners:
            listener.message_received(message, notifier = self)
    
    def start_service(self):
        """Do any initialization required to start sending and receiving SMS
        messages. For example start listening at some port for notifications.
        """
        _LOGGER.debug("Starting HTTP server to receive messages from Kannel...")
        self._server_thread = threading.Thread(target = self._notification_server.serve_forever)
        self._server_thread.setDaemon(True)
        self._server_thread.start()
        _LOGGER.info("Started HTTP server to receive messages from Kannel.")

    def stop_service(self):
        """Release any system resources acquired (sockets, files, etc.) and
        stop the threads or processes that were started.
        """
        _LOGGER.info("Stopping HTTP server that received messages from Kannel.")
        self._notification_server.shutdown()
        _LOGGER.info("Server stopped.")

    def send_message(self, message):
        """Send a message via SMS to the specified telephone number. If no
        SENDER_PHONE_NUMBER is specified in the message, the one give when
        KannelSMSGateway was instantiated will be used.
        """
        _LOGGER.info("Sending message to " 
                     + message.data[Message.RECEIVER_PHONE_NUMBER])
        url = "http://%s:%d/cgi-bin/sendsms?username=%s&password=%s&from=%s&to=%s&udh=%s&text=%s" \
                % (self._kannel_server_address
                   , self._kannel_server_port
                   , self._kannel_user, self._kannel_password
                   , message.data[Message.SENDER_PHONE_NUMBER] if message.data[Message.SENDER_PHONE_NUMBER] != None else self._kannel_phone_number
                   , message.data[Message.RECEIVER_PHONE_NUMBER]
                   , urllib.quote_plus(message.data[Message.UDH])
                   , urllib.quote_plus(message.data[Message.ORIGINAL_BINARY_MESSAGE]))
        _LOGGER.debug("Opening url: " + url)
        feed = urllib.urlopen(url)
        out = feed.read()
        _LOGGER.info("Send message operation returned: " + str(out))

class _MyHttpServer (ThreadingMixIn, HTTPServer):
    """A threading HTTPServer."""
    pass
